package com.atwbj.langchain4jbeta3learndemo.controller;

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.store.embedding.EmbeddingStore;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;

/**
 * @auther zzyybs@126.com
 * @Date 2025-06-02 20:47
 * @Description: 知识出处，https://docs.langchain4j.dev/tutorials/rag#embedding-store
 */
@RestController
@Slf4j
public class EmbeddinglController {
    @Resource
    private EmbeddingModel embeddingModel;
    @Resource
    private QdrantClient qdrantClient;
    @Resource
    private EmbeddingStore<TextSegment> embeddingStore;


    /**
     * 文本向量化测试，看看文本转换成的向量数据,结果如下，数组太长了，下面只是部分数据示例
     * Embedding { vector = [-0.03135365, 0.12742494, -0.08095504, -0.01212006,
     * -0.032706764, -0.03811923, 0.0118687665, 0.08049112, -0.07113528, -7.665648E-4,
     * -0.04453687, -0.036514822, -0.032938726, 0.009418659, -0.0074131466, -0.010844264,
     * 0.04755239, -0.026540417, 0.010447994, -0.0030686755, -0.034330506, 0.0614315,
     * 0.032146186, 0.013937103, -0.053080834, 0.029304642, -0.012129725, -0.08938303,
     * ........ ..... ..... ....... ........... ....... .......... .......
     * 0.018866314, -0.0068139094, 0.04291313, -0.056908224, 0.037288032, 0.03305471, 0.022191115, -6.8441126E-4,
     * -0.0061083557, -0.022983653, 0.09696048, -0.022365086, 0.06773315, -0.035065055, -0.03966565, 0.048596222,
     * 0.00726817, 0.0015923284, 0.037751958, 0.02584453] }
     * http://localhost:8888/embedding/embed
     *
     * @return
     */
    @GetMapping(value = "/embedding/embed")
    public String embed() {
        String prompt = """
                   咏鸡
                鸡鸣破晓光，
                红冠映朝阳。
                金羽披霞彩，
                昂首步高岗。
                """;
        // 调用通义千问的文本向量化模型，将文本转为向量
        Response<Embedding> embeddingResponse = embeddingModel.embed(prompt);

        System.out.println(embeddingResponse);

        return embeddingResponse.content().toString();
    }

    /**
     * 新建向量数据库实例和创建索引：test-qdrant
     * 类似mysql create database test-qdrant
     * 访问这个接口，就会成功创建一个向量数据库实例，在Qdrant的Web管理界面能看到
     * http://localhost:8888/embedding/createCollection
     */
    @GetMapping(value = "/embedding/createCollection")
    public void createCollection() {

        // io.qdrant.client.grpc.Collections;
        // 使用Collections.VectorParams.newBuilder()构建向量参数，设置距离计算方式为余弦距离，向量维度为1024，这不细讲为什么这么设置，去看向量数据库相关知识
        // 调用qdrantClient.createCollectionAsync()异步创建集合
        var vectorParams = Collections.VectorParams.newBuilder()
                .setDistance(Collections.Distance.Cosine)
                .setSize(1024)
                .build();
        qdrantClient.createCollectionAsync("test-qdrant", vectorParams);
    }

    /*
 * 往向量数据库新增文本记录
 * 主要功能是创建一个文本片段，生成对应的向量嵌入，然后存储到向量数据库中
 * 在代码中使用 TextSegment 创建文本片段有以下几个重要好处：
 * 总的来说，使用 TextSegment 而不是直接使用字符串，主要是为了提供更强大和灵活的文本管理能力，特别是在需要元数据和高级搜索功能的场景下。
 * 1. 元数据支持
 *  TextSegment 允许附加元数据（metadata），如作者、来源、时间等信息，这些元数据可以在后续的搜索和过滤中使用，增强检索能力
 * 2.结构化数据管理：相比直接使用纯文本字符串，TextSegment 提供了更结构化的数据表示，可以统一管理文本内容及其相关属性
 * 3.增强搜索功能
 * 在 query2 中使用元数据过滤 .filter(metadataKey("author").isEqualTo("zzyy2"))
    支持基于元数据的高级搜索和过滤，可以实现更精确的检索结果
  4.框架集成：
   TextSegment 是 LangChain4j 框架的标准数据结构；与EmbeddingModel和EmbeddingStore等组件无缝集成；提供一致的API使用体验；
  5.5. 扩展性：便于后续功能扩展，如添加更多文本属性；支持更复杂的文本处理流程
总的来说，使用 TextSegment 而不是直接使用字符串，主要是为了提供更强大和灵活的文本管理能力，特别是在需要元数据和高级搜索功能的场景下。
 * @return String 返回存储操作的结果标识符，例如：2637e885-d851-4068-97fa-87837fffe53f
 */
    @GetMapping(value = "/embedding/add")
    public String add() {
        // 定义要存储的文本内容，这里是一首咏鸡诗
        String prompt = """
                咏鸡
                鸡鸣破晓光，
                红冠映朝阳。
                金羽披霞彩，
                昂首步高岗。
                """;

        // 创建文本片段对象并添加元数据
        TextSegment segment1 = TextSegment.from(prompt);
        segment1.metadata().put("author", "wbj");

        // 生成文本片段的向量嵌入
        Embedding embedding1 = embeddingModel.embed(segment1).content();

        // 将向量嵌入和文本片段存储到向量数据库中
        String result = embeddingStore.add(embedding1, segment1);

        System.out.println(result);

        return result;
    }


    /**
     * 处理/embedding/query1路径的GET请求，用于查询与"咏鸡说的是什么"相关的嵌入向量匹配结果
     * 该方法不接收参数，无返回值，直接将查询结果打印到控制台
     */
    @GetMapping(value = "/embedding/query1")
    public void query1() {
        // 生成查询文本的嵌入向量
        Embedding queryEmbedding = embeddingModel.embed("咏鸡说的是什么").content();

        // 构建嵌入搜索请求，设置查询向量和最大返回结果数
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(queryEmbedding)
                .maxResults(1)
                .build();

        // 执行嵌入向量搜索
        EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest);

        // 输出匹配结果中的文本内容
        System.out.println(searchResult.matches().get(0).embedded().text());
    }


    /**
     * 处理"/embedding/query2"路径的GET请求，执行嵌入式查询操作
     * 该方法演示了如何使用嵌入式模型进行文本查询，并在嵌入式存储中搜索匹配结果
     *
     * @return 无返回值
     */
    @GetMapping(value = "/embedding/query2")
    public void query2() {

        // 生成查询文本"咏鸡"的嵌入向量
        Embedding queryEmbedding = embeddingModel.embed("咏鸡").content();

        // 构建嵌入式搜索请求，设置查询向量、过滤条件和最大返回结果数
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(queryEmbedding)
                .filter(metadataKey("author").isEqualTo("wbj"))
                .maxResults(1)
                .build();

        // 执行嵌入式搜索操作
        EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest);

        // 输出搜索结果中第一个匹配项的文本内容
        System.out.println(searchResult.matches().get(0).embedded().text());
    }

}

